import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class IODemo6 {
    // 使用字节流来读取文件
    public static void main(String[] args) throws IOException {
        // 先去构造一个文件的对象
        // 创建 InputStream 对象的时候, 使用绝对的路径或者相对路径, 都是可以的 , 也可以使用File对象
        InputStream fileInputStream1 = new FileInputStream("d:/test.txt");
        // InputStream / OutputStream / Reader / Writer 都是抽象类, 不能直接 new
        // (抽象类 和 接口 有什么区别 )

        // 接下来就可以进行一个读操作
        // 目前我们不知道这个文件到底有多少
        // 所以用while循环来读取

        // fileInputStream.read();
        // read 这里的read 有三个版本
        // 1. 无参数版本: 一次读一个字节
        // 2. 一个参数版本:(是一个数组类型的参数) 把读到的内容填充到参数的这个字节数组中 (此处的参数是"输出型参数"); 返回值 是 实际读取的字节数
        // 3. 三个参数版本: 和 2 类似 , 只不过是 往数组的一部分区间里进行填充

        // 一次读取一个字节的操作:
       /* while(true){
            int b = fileInputStream.read(); // (这里面的读取类型是 一个字节 但是为什么 返回类型是int呢? 这里要考虑到一种情况: 读取到-1
            // 除了要表示 byte 里的 0->255 (-128 - 127) 这样的情况之外, 那还有一个特殊情况 -1
            // 这个情况表示读取文件结束了(读到文件末尾了))
            if(b == -1){
                // 读取完毕
                break;
            }
            System.out.printf("%x ",(byte)b); // 打印读取到的字节 需要强制转换成 byte

         // 这里面运行的结果是 :104 101 108 108 111
        // 读的每个字节, 就是一个这样的数字
        // 其实这些数字就是hello的ascii码
        // 如果在这个文件里面改成中文你好
        // 会读取到这样的数字
        // -28 -67 -96 -27 -91 -67
        // 这样的数字是怎么来的呢,就表示 你好 这两个字的编码方式
        // (可以使用 十六进制 来打印)
        // e4 bd a0 e5 a5 bd  这就是十六进制的结果 , 可以用(用 utf8 的)码表来查看
        }*/

        // read 第二个操作, 一次读取多个字节
        // (第三种写法 和 第二种写法差不多)
        try ( InputStream fileInputStream = new FileInputStream("d:/test.txt");){
            while(true){
                byte[] buffer = new byte[1024];
                // 此处 起 的名字为什么叫做 buffer
                // 缓冲区 (buffer)
                // buffer 存在的意义 就是为了 提高IO操作效率
                // (单词操作 IO , 是 要访问硬盘 IO 设备 . 单词操作是比较消耗时间的.)
                // (如果频繁进行这样的IO 操作, 耗时肯定就更多了 , 单次 IO 时间是一定的 , 如果能缩短IO 的次数, 此时就可以提高程序整体的效率了)
                // (第一个版本的代码, 是一次读取一个字节, 循环次数就比较高, read 次数也很高)
                // (第二个版本,是一次读取1024字节,循环次数就降低了很多. read 次数就变少了 , (两个版本的一次IO操作的时间都是一样的))
                // (这个数组就起了一个单独的名字 buffer 缓冲区 (缓和了一下冲突), 减小冲击的次数)

                int len =  fileInputStream.read(buffer); // 返回值类型也是 int 表示 实际读, 读到了几个字节
                System.out.println("len: "+len); //读取len 的日志
                if(len == -1){
                    break;
                }
                // 此时读取到的结果就被放到了 byte 的数组中
                // 来打印这个数组
                for (int i = 0; i < len; i++) {
                    System.out.printf("%x\n",buffer[i]);
                    // 运行结果和上一个版本的一致
                }
                // read 的第二个版本, 需要调用者提前准备好一个数组.
                //  fileInputStream.read(buffer);  这里的传参操作,相当于是把刚才准备好的数组, 交给 read 放啊, 让 read 方法内部针对这个数组进行填写
                // (此处的参数相当于 "输出型参数")
                // java 中一般 习惯做法 , 输入的信息作为参数, 输出的信息作为返回值
                // 但是也有少数情况,是用来使用参数来返回内容
                // 注意理解此时 read 的行为和返回值
                // read 会尽可能的把参数传进来的数组填满(比如 上面的数组长度是 1024 (希望读取的长度), read 就会尽可能的读取1024个字节,填充到数组里)
                // 但实际上, 文件剩余长度是有限的, 如果剩余长度超过1024,此时 1024 个字节都会填满, 返回值就是 1024 了
                // 如果当前剩余的长度不足 1024 , 此时有多少就会填多少, read 方法就会 返回当前实际读取的长度 (实际读取到的长度)
                // 第二轮读取的数据会覆盖第一轮读取的数据(用同一个数组循环读取 )
                // 当读到最后一次时, 有多少字节就读取多少字节
                // 然后再进行下一轮循环 , read 无任何读取内容的时候 返回 -1 循环结束

            }
        } /*finally {
            fileInputStream.close();
        }*/

        // 用完之后 要记得关闭
       // fileInputStream.close();
        // close 操作,含义是 关闭文件 , 关闭文件是在关闭什么?
        // 每次打开文件操作, 就会在文件描述符表中,申请一个位置, 把这个信息放进去
        // 每次关闭文件, 也就会把这个文件描述符表对应的表项给释放掉 (释放表里面的资源) (close就是做这样的操作)
        // 如果 close 操作没写,会怎么样呢?
        // 这就意味着 对应的表象没有及时释放, 虽然java 有GC(垃圾回收),GC 操作会在回收这个outputStream 对象的时候去完成这个释放操作
        // 但是GC不一定及时....
        // 如果不手动释放, 意味着 文件描述符表很可能 很快就被占满了
        // (这个数组, 不能自动扩容,存在上限的!!)
        // 如果占满了之后 , 后面再次打开文件, 就会打开失败
        // (文件描述符表最大长度, 不同系统上不太一样,但基本上就是 几百个到几千个左右...)

        // close 一般来说是要去执行到的.
        // 但是一个程序,这里的文件对象要自始至终的使用.不关闭问题也不大
        // 随着进程的结束,PCB销毁了, 文件描述符表也就销毁了. 对应的资源操作系统就自动回收了.
        // (如果你的文件close之后,程序立即就结束了, 你不close也没事了)
        // 当然一般写代码还是要注意这个操作的

        // 那么如何确保这个close 执行到呢?
        // 用 try 和 finally

        // 但是这种写法不是特别优雅'.. 可以这样写
        // 用 try(里面加上 对象和实例)
        // 而且就不用写close 了
        // 虽然没有写 , 但是还是会执行的, 只要 try 语句块执行完毕, 就可以自动执行到 close !!!
        // 这个语法在 java 中称作 try with resources
        // try with resources  可以针对文件尽心操作的, 但不是随便一个对象 拿到try() 里就能自动释放的
        // 需要满足一定的要求
        // (需要实现 Closeable 接口) (这样的类才可以放到try的()中 , 呢个接口提供的方法就是 close方法)

    }
}
